/**
 * Copyright (c) 2010 Basil Shikin, BugStat Project
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.bugstat.generators;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bugstat.common.BugProvider;
import org.bugstat.common.TimeInterval;
import org.bugstat.common.bugs.Bug;
import org.bugstat.common.reports.Report;
import org.bugstat.common.utils.AnnotatedClassLoader;
import org.bugstat.common.utils.ProgressListener;

/**
 * This class is used to manage existing generators  
 *
 * @author Basil Shikin
 *
 */
public class GeneratorManager
{
    private static Log log = LogFactory.getLog( GeneratorManager.class );
    
    static
    {
        AnnotatedClassLoader.scanBase( GeneratorManager.class );
    }
    
    private final BugProvider bugProvider;
    
    
    /**
     * Create new report generator manager
     * 
     * @param bugProvider An instance that provides bug data. Must not be null.
     */
    public GeneratorManager(BugProvider bugProvider)
    {
        if ( bugProvider == null ) throw new IllegalArgumentException("Invalid bug provider specified");
        
        this.bugProvider = bugProvider;
    }
    
    /**
     * Use specified generators to generate reports for given time interval.
     * 
     * @param interval Interval to generate reports for. Must not be null.
     * @param generators Generators to use. Must not be null.
     * @param progressListener Progress listener to notify of the progress. May be null.
     * 
     * @return List of generated reports.
     * 
     * @throws IOException In case unable to generate reports 
     */
    public List<Report> generateReports( final TimeInterval interval, final List<ReportGenerator> generators, final ProgressListener progressListener ) throws IOException
    {
        if ( generators == null ) throw new IllegalArgumentException("Invalid generators specified");
     
        log.info("Generating reports for bugs from " + interval.getStartDate() + " to " + interval.getEndDate() + " using " + generators.size() + " report generators...");
        
        final ReportGeneratorContext context = createReportGeneratorContext( interval, generators );
        if ( progressListener != null ) progressListener.progressReported( 1 );
        
        fireStartedGeneratingReports( generators, interval, context );
        if ( progressListener != null ) progressListener.progressReported( 5 );
        
        final int totalPeriods = interval.countSteps();
        float currentPeriod = 1;
        
        Date fromDate = interval.getStartDate();
        Date toDate   = interval.retrieveNextDate( fromDate );

        // Create bug receiver
        final BugProvider.BugReceiver receiver = new BugProvider.BugReceiver() {
            public void processBug(Bug bug)
            {
                fireProcessBug(generators, bug);
            }
        };
        
        while ( toDate != null )
        {          
            log.debug("Processing bugs from " + fromDate + " to " + toDate + "..." );

            firePeriodStarted( generators, toDate );
            bugProvider.retrieveBugs( toDate, receiver);
            firePeriodEnded( generators );
            
            currentPeriod += 1;
            if ( progressListener != null ) progressListener.progressReported( 5 + Math.round( 90F*(currentPeriod/totalPeriods) ) );
            
            log.debug("Bugs from " + fromDate + " to " + toDate + " processed" );
            
            // Move to next date
            fromDate = toDate;
            toDate   = interval.retrieveNextDate( toDate );
        }
        fireFinishedGeneratingReports( generators );
        
        // Generate and return resulting list
        final List<Report> result = new ArrayList<Report>();
        for ( ReportGenerator generator : generators )
        {
            result.addAll( generator.getGeneratedReports() );
        }
        
        if ( progressListener != null ) progressListener.progressReported( 100 );
        
        log.info( result.size() + " reports generated");
        
        return result;
    }
    

    private void fireProcessBug(List<ReportGenerator> generators, Bug bug)
    {
        for ( ReportGenerator generator : generators )
        {
            generator.processBug( bug );
        }
    }

    private void firePeriodStarted(List<ReportGenerator> generators, Date currentDate)
    {
        for ( ReportGenerator generator : generators )
        {
            generator.periodStarted( currentDate );
        }
    }

    private void firePeriodEnded(List<ReportGenerator> generators)
    {
        for ( ReportGenerator generator : generators )
        {
            generator.periodEnded();
        }
    }

    private void fireStartedGeneratingReports(List<ReportGenerator> generators, TimeInterval interval, ReportGeneratorContext context)
    {
        for ( ReportGenerator generator : generators )
        {
            generator.startedGeneratingReports(interval, context );
        }
    }

    private void fireFinishedGeneratingReports(List<ReportGenerator> generators)
    {
        for ( ReportGenerator generator : generators )
        {
            generator.finishedGeneratingReports();
        }        
    }

    private ReportGeneratorContext createReportGeneratorContext( TimeInterval interval, List<ReportGenerator> generators )
    {
        return new ReportGeneratorContext(interval, generators, bugProvider);
    }

    /**
     * List all available report generators
     * 
     * @return List of all report generators
     */
    public static Collection<ReportGenerator> listGenerators()
    {
        return AnnotatedClassLoader.retrieveClassesWithAnnotation( CanGenerateReports.class, ReportGenerator.class );
    }
}
